package it.unicam.cs.cosy.bioshape.core.shapes;

import javax.vecmath.Point3d;
import javax.vecmath.Tuple3d;

import it.unicam.cs.cosy.bioshape.core.shapes.Bounds;

/**
 * This class defines a spherical bounding region which is defined by a center
 * point and a radius.
 */
public class BoundingSphere extends Bounds {

	/** Generated serialization ID */
	private static final long serialVersionUID = 1L;

	/** The center of the bounding sphere. */
	Point3d center = null;

	/** The radius of the bounding sphere. */
	double radius;

	/**
	 * Generates the Bounding Sphere. If the radius is <i>negative</i> an
	 * exception is thrown.
	 * 
	 * @param center
	 *            The spatial centre of the sphere
	 * @param radius
	 *            The radius of the sphere
	 */
	public BoundingSphere(Tuple3d center, double radius) {
		if (radius < 0)
			throw new IllegalArgumentException("Bounding sphere radius cannot"
					+ "be negative...");
		this.center = new Point3d(center);
		this.radius = radius;
		boundId = BOUNDING_SPHERE;
	}

	@Override
	protected Object clone() {
		return new BoundingSphere(this.center, this.radius);
	}

	@Override
	public boolean equals(Object bounds) {
		if (this == bounds)
			return true;
		try {
			BoundingSphere sphere = (BoundingSphere) bounds;
			return (center.equals(sphere.center) && radius == sphere.radius);
		} catch (NullPointerException e) {
			return false;
		} catch (ClassCastException e) {
			return false;
		}
	}

	@Override
	public boolean intersect(Bounds boundsObject) {
		if (boundsObject == null)
			return false;
		if (boundsObject.boundId == BOUNDING_BOX) {
			BoundingBox box = (BoundingBox) boundsObject;
			double dis = 0.0;
			double rad_sq = radius * radius;
			double app;
			if ((app = center.x - box.min.x) < 0) {
				if (app < -radius)
					return false;
				dis = app * app;
			} else if ((app = center.x - box.max.x) > 0) {
				if (app > radius)
					return false;
				dis = app * app;
			}
			if ((app = center.y - box.min.y) < 0) {
				if (app < -radius)
					return false;
				dis += app * app;
			} else if ((app = center.y - box.max.y) > 0) {
				if (app > radius)
					return false;
				dis += app * app;
			}
			if ((app = center.z - box.min.z) < 0) {
				if (app < -radius)
					return false;
				dis += app * app;
			} else if ((app = center.z - box.max.z) > 0) {
				if (app > radius)
					return false;
				dis += app * app;
			}
			// find the corner closest to the center of sphere
			// if (center.x < box.min.x)
			// dis = (center.x - box.min.x) * (center.x - box.min.x);
			// else if (center.x > box.max.x)
			// dis = (center.x - box.max.x) * (center.x - box.max.x);
			// if (center.y < box.min.y)
			// dis += (center.y - box.min.y) * (center.y - box.min.y);
			// else if (center.y > box.max.y)
			// dis += (center.y - box.max.y) * (center.y - box.max.y);
			// if (center.z < box.min.z)
			// dis += (center.z - box.min.z) * (center.z - box.min.z);
			// else if (center.z > box.max.z)
			// dis += (center.z - box.max.z) * (center.z - box.max.z);
			return (dis <= rad_sq);
		} else if (boundsObject.boundId == BOUNDING_SPHERE) {
			BoundingSphere sphere = (BoundingSphere) boundsObject;
			double radsq = radius + sphere.radius;
			radsq *= radsq;
			double distsq = center.distanceSquared(sphere.center);
			return (distsq <= radsq);
		} else
			throw new IllegalArgumentException("Wrong bounding box!");
	}

	@Override
	public boolean intersect(Tuple3d point) {
		double x = point.x - center.x;
		double y = point.y - center.y;
		double z = point.z - center.z;
		double dist = x * x + y * y + z * z;
		//
		if (dist > radius * radius)
			return false;
		return true;
	}

	@Override
	public void set(Bounds boundsObject) {
		if (boundsObject.boundId == BOUNDING_BOX) {
			BoundingBox box = (BoundingBox) boundsObject;
			center.x = (box.max.x + box.min.x) / 2.0;
			center.y = (box.max.y + box.min.y) / 2.0;
			center.z = (box.max.z + box.min.z) / 2.0;
			radius = 0.5 * Math.sqrt((box.max.x - box.min.x)
					* (box.max.x - box.min.x) + (box.max.y - box.min.y)
					* (box.max.y - box.min.y) + (box.max.z - box.min.z)
					* (box.max.z - box.min.z));

		} else if (boundsObject.boundId == BOUNDING_SPHERE) {
			BoundingSphere sphere = (BoundingSphere) boundsObject;
			radius = sphere.radius;
			center.x = sphere.center.x;
			center.y = sphere.center.y;
			center.z = sphere.center.z;
		} else
			throw new IllegalArgumentException("Invalid Bounding object!");
	}

	@Override
	public double getRadius() {
		return radius;
	}

	@Override
	public int hashCode() {
		long bits = 1L;
		bits = 31L * bits + Double.doubleToLongBits(radius);
		bits = 31L * bits + Double.doubleToLongBits(center.x);
		bits = 31L * bits + Double.doubleToLongBits(center.y);
		bits = 31L * bits + Double.doubleToLongBits(center.z);
		return (int) (bits ^ (bits >> 32));
	}

	@Override
	public void moveTo(double x, double y, double z) {
		center.set(x, y, z);
	}

	@Override
	public void moveTo(Tuple3d point) {
		center.x = point.x;
		center.y = point.y;
		center.x = point.z;
	}

	@Override
	public boolean contains(Bounds boundsObject) {
		if (boundsObject == null)
			return false;
		//
		if (boundsObject.boundId == BOUNDING_BOX) {
			BoundingBox box = (BoundingBox) boundsObject;
			if (box.min.distance(center) > radius
					|| box.max.distance(center) > radius)
				return false;
			Point3d p = new Point3d(box.min);
			p.x += box.max.x - box.min.x;
			if (p.distance(center) > radius)
				return false;
			p.y += box.max.y - box.min.y;
			if (p.distance(center) > radius)
				return false;
			p.set(box.min);
			p.z += box.max.z - box.min.z;
			if (p.distance(center) > radius)
				return false;
			p.x += box.max.x - box.min.x;
			if (p.distance(center) > radius)
				return false;
			p.set(box.min);
			p.y += box.max.y - box.min.y;
			if (p.distance(center) > radius)
				return false;
			p.z += box.max.z - box.min.z;
			if (p.distance(center) > radius)
				return false;
			return true;
		} else if (boundsObject.boundId == BOUNDING_SPHERE) {
			BoundingSphere sphere = (BoundingSphere) boundsObject;
			double distance = center.distance(sphere.center);
			if (radius < Math.abs(distance) + sphere.radius)
				return false;
			return false;
		} else
			throw new IllegalArgumentException("Wrong bounding box!");
	}

	@Override
	public double getMin(int index) {
		if (index == 0)
			return center.x - radius;
		if (index == 1)
			return center.y - radius;
		return center.z - radius;
	}

	@Override
	public double getMax(int index) {
		if (index == 0)
			return center.x + radius;
		if (index == 1)
			return center.y + radius;
		return center.z + radius;
	}

	@Override
	public Point3d getCenter() {
		return center;
	}

	@Override
	public void translateOf(Tuple3d offset) {
		translateOf(offset.x, offset.y, offset.z);
	}

	@Override
	public void translateOf(double x, double y, double z) {
		center.x += x;
		center.y += y;
		center.x += z;
	}
}